1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.webmacro.servlet;
25
26 import org.webmacro.*;
27 import org.webmacro.util.LogSystem;
28
29 import javax.servlet.ServletConfig;
30 import javax.servlet.ServletException;
31 import javax.servlet.http.HttpServlet;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.servlet.http.HttpServletResponse;
34 import java.io.*;
35 import java.lang.reflect.Method;
36 import java.util.Locale;
37
38 /***
39 * This is an abstract base class which can be used
40 * to implement a kind of WebMacro servlet. You
41 * can either subclass from it directly, or make use of one of the
42 * generic subclasses provided.
43 * <p>
44 * It's primary function is to create a WebContext and manage a
45 * Broker. It also provides a couple of convenience functions
46 * that access the Broker and/or WebContext to make some commonly
47 * accessed services more readily available.
48 * <p>
49 * @see org.webmacro.Broker
50 */
51 abstract public class WMServlet extends HttpServlet implements WebMacro
52 {
53
54 private WebMacro _wm = null;
55 private Broker _broker = null;
56 private boolean _started = false;
57 /***
58 * The name of the config entry we look for to find out what to
59 * call the variable used in the ERROR_TEMPLATE
60 */
61 final static String ERROR_VARIABLE = "ErrorVariable";
62
63 /***
64 * The name of the error template we will use if something
65 * goes wrong
66 */
67 final static String ERROR_TEMPLATE = "ErrorTemplate";
68
69 /***
70 * Defaults for error variable and error template
71 */
72 final static String ERROR_TEMPLATE_DEFAULT = "error.wm";
73 final static String ERROR_VARIABLE_DEFAULT = "error";
74
75 /***
76 * Log object used to write out messages
77 */
78 protected Log _log;
79
80 /***
81 * null means all OK
82 */
83 private String _problem = "Not yet initialized: Your servlet API tried to access WebMacro without first calling init()!!!";
84
85 /***
86 * This is the old-style init method, it just calls init(), after
87 * handing the ServletConfig object to the superclass
88 * @exception ServletException if it failed to initialize
89 */
90 public synchronized void init (ServletConfig sc)
91 throws ServletException
92 {
93 super.init(sc);
94 init();
95 }
96
97 /***
98 * This method is called by the servlet runner--do not call it. It
99 * must not be overidden because it manages a shared instance
100 * of the broker--you can overide the start() method instead, which
101 * is called just after the broker is initialized.
102 */
103 public synchronized void init ()
104 {
105
106 if (_started)
107 {
108 return;
109 }
110
111
112
113 if (_wm == null)
114 {
115 try
116 {
117 _wm = initWebMacro();
118 _broker = _wm.getBroker();
119 }
120 catch (InitException e)
121 {
122 _problem = "Could not initialize the broker!\n\n"
123 + "*** Check that WebMacro.properties was in your servlet\n"
124 + "*** classpath, in a similar place to webmacro.jar \n"
125 + "*** and that all values were set correctly.\n\n"
126 + e.getMessage();
127 Log sysLog = LogSystem.getSystemLog("servlet");
128 sysLog.error(_problem, e);
129 return;
130 }
131 }
132 _log = _broker.getLog("servlet", "WMServlet lifecycle information");
133
134 try
135 {
136 if (_log.loggingDebug())
137 {
138 java.net.URL url = getBroker().getResource(Broker.WEBMACRO_PROPERTIES);
139 if (url != null)
140 _log.debug("Using properties from " + url.toExternalForm());
141 else
142 _log.debug("No WebMacro.properties file was found.");
143 }
144 start();
145 _problem = null;
146 }
147 catch (ServletException e)
148 {
149 _problem = "WebMacro application code failed to initialize: \n"
150 + e + "\n" + "This error is the result of a failure in the\n"
151 + "code supplied by the application programmer.\n";
152 _log.error(_problem, e);
153 }
154 _log.notice("started: " + this);
155 _started = true;
156
157 }
158
159 /***
160 * This method is called by the servlet runner--do not call it. It
161 * must not be overidden because it manages a shared instance of
162 * the broker--you can overide the stop() method instead, which
163 * will be called just before the broker is shut down. Once the
164 * stop() method has been called, the nested WM will be destroyed
165 * and then the parent destroy() method will be invoked.
166 * @see WM#destroy()
167 */
168 public synchronized void destroy ()
169 {
170 stop();
171 _wm.destroy();
172 _log.notice("stopped: " + this);
173 _wm = null;
174 _started = false;
175 super.destroy();
176 }
177
178
179
180
181 /***
182 * Process an incoming GET request: Builds a WebContext up and then
183 * passes it to the handle() method. You can overide this if you want,
184 * though for most purposes you are expected to overide handle()
185 * instead.
186 * <p>
187 * @param req the request we got
188 * @param resp the response we are generating
189 * @exception ServletException if we can't get our configuration
190 * @exception IOException if we can't write to the output stream
191 */
192 protected void doGet (HttpServletRequest req, HttpServletResponse resp)
193 throws ServletException, IOException
194 {
195 doRequest(req, resp);
196 }
197
198 /***
199 * Behaves exactly like doGet() except that it reads data from POST
200 * before doing exactly the same thing. This means that you can use
201 * GET and POST interchangeably with WebMacro. You can overide this if
202 * you want, though for most purposes you are expected to overide
203 * handle() instead.
204 * <p>
205 * @param req the request we got
206 * @param resp the response we are generating
207 * @exception ServletException if we can't get our configuration
208 * @exception IOException if we can't read/write to the streams we got
209 */
210 protected void doPost (HttpServletRequest req, HttpServletResponse resp)
211 throws ServletException, IOException
212 {
213 doRequest(req, resp);
214 }
215
216 final private void doRequest (
217 HttpServletRequest req, HttpServletResponse resp)
218 throws IOException
219 {
220
221 WebContext context = null;
222
223 if (_problem != null)
224 {
225 init();
226 if (_problem != null)
227 {
228 try
229 {
230 resp.setContentType("text/html");
231 Writer out = resp.getWriter();
232
233 out.write("<html><head><title>WebMacro Error</title></head>");
234 out.write("<body><h1><font color=\"red\">WebMacro Error: ");
235 out.write("</font></h1><pre>");
236 out.write(_problem);
237 out.write("</pre>");
238 out.write("Please contact the server administrator");
239 out.flush();
240 out.close();
241 }
242 catch (Exception e)
243 {
244 _log.error(_problem, e);
245 }
246 return;
247 }
248 }
249
250
251 context = newWebContext(req, resp);
252 try
253 {
254 Template t;
255 t = handle(context);
256
257 if (t != null)
258 {
259 execute(t, context);
260 }
261 destroyContext(context);
262 }
263 catch (HandlerException e)
264 {
265 _log.error("Your handler failed to handle the request:" + this, e);
266 Template tmpl = error(context,
267 "Your handler was unable to process the request successfully " +
268 "for some reason. Here are the details:<p>" +
269 "<pre>" + e + "</pre>");
270 execute(tmpl, context);
271 }
272 catch (Exception e)
273 {
274 _log.error("Your handler failed to handle the request:" + this, e);
275 Template tmpl = error(context,
276 "The handler WebMacro used to handle this request failed for " +
277 "some reason. This is likely a bug in the handler written " +
278 "for this application. Here are the details:<p>" +
279 "<pre>" + e + "</pre>");
280 execute(tmpl, context);
281 }
282 }
283
284
285
286
287 /***
288 * Create an error template using the built in error handler.
289 * This is useful for returning error messages on failure;
290 * it is used by WMServlet to display errors resulting from
291 * any exception that you may throw from the handle() method.
292 * @param context will add error variable to context (see Config)
293 * @param error a string explaining what went wrong
294 */
295 protected Template error (WebContext context, String error)
296 {
297 Template tmpl = null;
298
299 try
300 {
301 context.put(getErrorVariableName(),
302 error);
303
304 tmpl = getErrorTemplate();
305 }
306 catch (Exception e2)
307 {
308 _log.error("Unable to use ErrorHandler", e2);
309 }
310 return tmpl;
311 }
312
313 /***
314 * <p>Returns the name of the error variable, as per the config.</p>
315 * @return Name to use for the error variable in templates
316 */
317 protected String getErrorVariableName ()
318 {
319 return getConfig(ERROR_VARIABLE, ERROR_VARIABLE_DEFAULT);
320 }
321
322 /***
323 * This object is used to access components that have been plugged
324 * into WebMacro; it is shared between all instances of this class and
325 * its subclasses. It is created when the first instance is initialized,
326 * and deleted when the last instance is shut down. If you attempt to
327 * access it after the last servlet has been shutdown, it will either
328 * be in a shutdown state or else null.
329 */
330 public Broker getBroker ()
331 {
332
333
334
335
336
337 return _broker;
338 }
339
340 /***
341 * Get a Log object which can be used to write to the log file.
342 * Messages to the logfile will be associated with the supplied
343 * type. The type name should be short as it may be printed on
344 * every log line. The description is a longer explanation of
345 * the type of messages you intend to write to this Log.
346 */
347 public Log getLog (String type, String description)
348 {
349 return _broker.getLog(type, description);
350 }
351
352 /***
353 * Get a Log object which can be used to write to the log file.
354 * Messages to the logfile will be associated with the supplied
355 * type. The type will be used as the description.
356 */
357 public Log getLog (String type)
358 {
359 return _broker.getLog(type, type);
360 }
361
362 /***
363 * Retrieve a template from the "template" provider. Equivalent to
364 * getBroker().get(TemplateProvider.TYPE,key)
365 * @exception NotFoundException if the template was not found
366 * @exception ResourceException if the template coult not be loaded
367 */
368 public Template getTemplate (String key)
369 throws ResourceException
370 {
371 return _wm.getTemplate(key);
372 }
373
374 /***
375 * Retrieve a URL. This is largely equivalent to creating a URL
376 * object and requesting its content, though it will sit in
377 * WebMacro's cache rather than re-requesting each time.
378 * The content will be returned as an Object.
379 */
380 public String getURL (String url)
381 throws ResourceException
382 {
383 return _wm.getURL(url);
384 }
385
386
387 /***
388 * Retrieve configuration information from the "config" provider.
389 * Equivalent to getBroker().get(Config.TYPE,key)
390 * @exception NotFoundException could not locate requested information
391 */
392 public String getConfig (String key)
393 throws NotFoundException
394 {
395 return _wm.getConfig(key);
396 }
397
398 /***
399 * Retrieve configuration information from the "config" provider.
400 * Return specified default if key could not be found
401 */
402 public String getConfig (String key, String defaultValue)
403 {
404 try
405 {
406 return _wm.getConfig(key);
407 }
408 catch (NotFoundException e)
409 {
410 return defaultValue;
411 }
412 }
413
414 /***
415 * Create a new Context object
416 */
417 public Context getContext ()
418 {
419 return _wm.getContext();
420 }
421
422 /***
423 * Create a new WebContext object; can be overridden
424 */
425 public WebContext getWebContext (HttpServletRequest req, HttpServletResponse res)
426 {
427 return _wm.getWebContext(req, res);
428 }
429
430 /***
431 * Convenience method for writing a template to an OutputStream.
432 * This method takes care of all the typical work involved
433 * in writing a template.<p>
434 *
435 * This method uses the default <code>TemplateOutputEncoding</code> specified in
436 * WebMacro.defaults or your custom WebMacro.properties.
437 *
438 * @param templateName name of Template to write. Must be accessible
439 * via TemplatePath
440 * @param out where the output of the template should go
441 * @param context The Context (can be a WebContext too) used
442 * during the template evaluation phase
443 * @throws java.io.IOException if the template cannot be written to the
444 * specified output stream
445 * @throws ResourceException if the template name specified cannot be found
446 * @throws PropertyException if a fatal error occured during the Template
447 * evaluation phase
448 */
449 public void writeTemplate (String templateName, java.io.OutputStream out,
450 Context context)
451 throws java.io.IOException, ResourceException, PropertyException
452 {
453
454 writeTemplate(templateName, out,
455 getConfig(WMConstants.TEMPLATE_OUTPUT_ENCODING),
456 context);
457 }
458
459 /***
460 * Convienence method for writing a template to an OutputStream.
461 * This method takes care of all the typical work involved
462 * in writing a template.
463 *
464 * @param templateName name of Template to write. Must be accessible
465 * via TemplatePath
466 * @param out where the output of the template should go
467 * @param encoding character encoding to use when writing the template
468 * if the encoding is <code>null</code>, the default
469 * <code>TemplateOutputEncoding</code> is used
470 * @param context The Context (can be a WebContext too) used
471 * during the template evaluation phase
472 * @throws java.io.IOException if the template cannot be written to the
473 * specified output stream
474 * @throws ResourceException if the template name specified cannot be found
475 * @throws PropertyException if a fatal error occured during the Template
476 * evaluation phase
477 */
478 public void writeTemplate (String templateName, java.io.OutputStream out,
479 String encoding, Context context)
480 throws java.io.IOException, ResourceException, PropertyException
481 {
482
483 if (encoding == null)
484 encoding = getConfig(WMConstants.TEMPLATE_OUTPUT_ENCODING);
485
486 Template tmpl = getTemplate(templateName);
487 tmpl.write(out, encoding, context);
488 }
489
490
491
492
493 /***
494 * This method takes a populated context and a template and
495 * writes out the interpreted template to the context's output
496 * stream.
497 */
498 protected void execute (Template tmpl, WebContext c)
499 throws IOException
500 {
501 try
502 {
503 HttpServletResponse resp = c.getResponse();
504
505 Locale locale = (Locale) tmpl.getParam(
506 WMConstants.TEMPLATE_LOCALE);
507 if (_log.loggingDebug())
508 _log.debug("TemplateLocale=" + locale);
509 if (locale != null)
510 {
511 setLocale(resp, locale);
512 }
513
514 String encoding = (String) tmpl.getParam(
515 WMConstants.TEMPLATE_OUTPUT_ENCODING);
516 if (encoding == null)
517 {
518 encoding = resp.getCharacterEncoding();
519 }
520
521 if (_log.loggingDebug())
522 _log.debug("Using output encoding " + encoding);
523
524
525
526
527
528 byte[] bytes = tmpl.evaluateAsBytes(encoding, c);
529
530
531
532 writeResponseBytes(resp, bytes, encoding);
533 }
534 catch (UnsupportedEncodingException e)
535 {
536
537
538
539 _log.error("tried to use an unsupported encoding", e);
540 throw e;
541 }
542 catch (IOException e)
543 {
544
545 }
546 catch (Exception e)
547 {
548 String error =
549 "WebMacro encountered an error while executing a template:\n"
550 + ((tmpl != null) ? (tmpl + ": " + e + "\n") :
551 ("The template failed to load; double check the "
552 + "TemplatePath in your webmacro.properties file."));
553 _log.error(error, e);
554 try
555 {
556 Template errorTemplate = error(c,
557 "WebMacro encountered an error while executing a template:\n"
558 + ((tmpl != null) ? (tmpl + ": ")
559 : ("The template failed to load; double check the "
560 + "TemplatePath in your webmacro.properties file."))
561 + "\n<pre>" + e + "</pre>\n");
562
563 String err = errorTemplate.evaluateAsString(c);
564 c.getResponse().getWriter().write(err);
565 }
566 catch (Exception errExcept)
567 {
568 _log.error("Error writing error template!", errExcept);
569 }
570 }
571 }
572
573 /***
574 * Helper method to write out a FastWriter (that has bufferd
575 * the response) to a ServletResponse. This method will try to use
576 * the response's OutputStream first and if this fails, fall back
577 * to its Writer.
578 * @param response where to write fast writer to
579 * @param bytes the bytes to write
580 */
581 private void writeResponseBytes (HttpServletResponse response, byte[] bytes, String encoding)
582 throws IOException
583 {
584 OutputStream out;
585
586 try
587 {
588 out = response.getOutputStream();
589 }
590 catch (IllegalStateException e)
591 {
592
593
594
595
596
597
598
599
600 out = null;
601 _log.debug("Using Writer instead of OutputStream");
602 }
603 response.setContentLength(bytes.length);
604 if (out != null)
605 {
606 out.write(bytes);
607 }
608 else
609 {
610 response.getWriter().write(new String(bytes, encoding));
611 }
612 }
613
614
615
616
617
618 /***
619 * This method is called at the beginning of a request and is
620 * responsible for providing a Context for the request. The
621 * default implementation calls WebContext.newInstance(req,resp)
622 * on the WebContext prototype returned by the initWebContext() method.
623 * This is probably suitable for most servlets, though you can override
624 * it and do something different if you like. You can throw a
625 * HandlerException if something goes wrong.
626 */
627 public WebContext newContext (
628 HttpServletRequest req, HttpServletResponse resp)
629 throws HandlerException
630 {
631 return _wm.getWebContext(req, resp);
632
633 }
634
635 /***
636 * This method is called to handle the processing of a request. It
637 * should analyze the data in the request, put whatever values are
638 * required into the context, and return the appropriate view.
639 * @return the template to be rendered by the WebMacro engine
640 * @exception HandlerException throw this to produce vanilla error messages
641 * @param context contains all relevant data structures, incl builtins.
642 */
643 public abstract Template handle (WebContext context)
644 throws HandlerException;
645
646
647 /***
648 * This method is called at the end of a request and is responsible
649 * for cleaning up the Context at the end of the request. You may
650 * not need to do anything here, but it is sometimes important if
651 * you have an open database connection in your context that you
652 * need to close. The default implementation calls wc.clear().
653 */
654 public void destroyContext (WebContext wc)
655 throws HandlerException
656 {
657 }
658
659
660 /***
661 * Override this method to implement any startup/init code
662 * you require. The broker will have been created before this
663 * method is called; the default implementation does nothing.
664 * This is called when the servlet environment initializes
665 * the servlet for use via the init() method.
666 * @exception ServletException to indicate initialization failed
667 */
668 protected void start () throws ServletException
669 {
670 }
671
672 /***
673 * Override this method to implement any shutdown code you require.
674 * The broker may be destroyed just after this method exits. This
675 * is called when the servlet environment shuts down the servlet
676 * via the shutdown() method. The default implementation does nothing.
677 */
678 protected void stop ()
679 {
680 }
681
682
683 /***
684 * This method returns the WebMacro object which will be used to load,
685 * access, and manage the Broker. The default implementation is to
686 * return a new WM() object. You could override it and return a WM
687 * object constructed with a particular configuration file, or some
688 * other implementation of the WebMacro interface.
689 */
690 public WebMacro initWebMacro () throws InitException
691 {
692 return new WM(this);
693 }
694
695 /***
696 * NO LONGER USED
697 * Exists only to catch implementations that use it.
698 * Use newWebContext instead.
699 * @deprecated
700 */
701 public final WebContext initWebContext () throws InitException
702 {
703 return null;
704 }
705
706 public WebContext newWebContext(HttpServletRequest req, HttpServletResponse resp) {
707 return new WebContext(_broker, req, resp);
708 }
709
710 /***
711 * Set the locale on the response. The reflection trickery is because
712 * this is only defined for JSDK 2.2+
713 */
714 protected void setLocale (HttpServletResponse resp, Locale locale)
715 {
716 try
717 {
718 Method m = HttpServletResponse.class.getMethod(
719 "setLocale",
720 new Class[]
721 {Locale.class});
722 m.invoke(resp, (Object[])new Locale[]
723 {locale});
724 if (_log.loggingDebug())
725 _log.debug("Successfully set locale to " + locale);
726 }
727 catch (Exception e)
728 {
729 if (_log.loggingDebug())
730 _log.debug("Error set locale to " + locale + ": " + e.getClass());
731 }
732 }
733
734 /***
735 * Get a new FastWriter.
736 * A FastWriter is used when writing templates to an output stream
737 *
738 * @param out The output stream the FastWriter should write to. Typically
739 * this will be your ServletOutputStream
740 * @param enctype the Encoding type to use
741 * @deprecated
742 */
743 public FastWriter getFastWriter (OutputStream out, String enctype)
744 throws UnsupportedEncodingException
745 {
746 return _wm.getFastWriter(out, enctype);
747 }
748
749
750 private static final String DEFAULT_ERROR_TEXT =
751 "<HTML><HEAD><TITLE>Error</TITLE></HEAD>\n"
752 + "#set $Response.ContentType = \"text/html\"\n"
753 + "<BODY><H1>Error</H1>"
754 + "<HR>$error</BODY></HTML>";
755
756 private Template _errorTemplate = null;
757
758 /***
759 * Gets a template for displaying an error message.
760 * Tries to get configured template, then default template
761 * and lastly constructs a string template.
762 * @return A Template which can be used to format an error message
763 */
764 public Template getErrorTemplate ()
765 {
766 String templateName = getErrorTemplateName();
767
768 try
769 {
770 _errorTemplate = (Template) _broker.get("template", templateName);
771 }
772 catch (ResourceException e)
773 {
774 _errorTemplate = new org.webmacro.engine.StringTemplate(_broker, DEFAULT_ERROR_TEXT,
775 "WebMacro default error template");
776 }
777 return _errorTemplate;
778 }
779
780 protected String getErrorTemplateName ()
781 {
782 String templateName;
783
784 try
785 {
786 templateName = (String) _broker.get("config", ERROR_TEMPLATE);
787 }
788 catch (ResourceException e)
789 {
790 templateName = ERROR_TEMPLATE_DEFAULT;
791 }
792 return templateName;
793 }
794
795
796 }